package gogm

/*
#include <string.h>
#include <openssl/err.h>
#include <openssl/pem.h>

EC_KEY* FromHexKey(int nid, const char* hexkey)
{
    BIGNUM *bn = NULL;
    BN_CTX *ctx = NULL;
    EC_POINT *point = NULL;
    EC_KEY* eckey = NULL;
    EC_GROUP *group = NULL;

    int ok = 0;
    if (!BN_hex2bn(&bn, hexkey)) return NULL;
    if (!(eckey = EC_KEY_new_by_curve_name(nid))) goto err;
    if (!EC_KEY_set_private_key(eckey, bn)) goto err;

	if (!(group = EC_GROUP_new_by_curve_name(nid))) goto err;
    EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
    if (!EC_KEY_set_group(eckey, group)) goto err;

    if (!(point = EC_POINT_new(group))) goto err;
    if (!(ctx = BN_CTX_new())) goto err;

    if (!EC_POINT_mul(group, point, bn, NULL, NULL, ctx)) goto err;
    if (!EC_KEY_set_public_key(eckey, point)) goto err;
    ok = 1;

err:
    if (group) EC_GROUP_free(group);
    if (bn) BN_free(bn);
    if (point) EC_POINT_free(point);
    if (ctx) BN_CTX_free(ctx);
    if (!ok && eckey) {
        EC_KEY_free(eckey);
        eckey = NULL;
    }

    return eckey;
}
*/
import "C"
import (
	"errors"
	"runtime"
	"unsafe"
)

var (
	ErrNoCurve = errors.New("no curve")
	ErrKey     = errors.New("key error")
)

var _nid_ = C.int(C.NID_SM2_General)

type PublicKey struct {
	pkey *C.EVP_PKEY
}

type PrivateKey struct {
	pkey *C.EVP_PKEY
}

type EC_KEY struct {
	eckey *C.EC_KEY
}

func NewECKey() (*EC_KEY, error) {
	eckey := C.EC_KEY_new()
	group := C.EC_GROUP_new_by_curve_name(_nid_)

	C.EC_KEY_set_asn1_flag(eckey, C.OPENSSL_EC_NAMED_CURVE)

	C.EC_KEY_set_group(eckey, group)
	C.EC_KEY_generate_key(eckey)

	ret := &EC_KEY{eckey}
	runtime.SetFinalizer(ret, func(ret *EC_KEY) {
		C.EC_KEY_free(ret.eckey)
	})

	return ret, nil
}

func PEM_Read_ECKey(filename, pass string) (*EC_KEY, error) {
	cfilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cfilename))

	cmode := C.CString("r")
	defer C.free(unsafe.Pointer(cmode))

	bio := C.BIO_new_file(cfilename, cmode)
	if bio == nil {
		return nil, ErrKey
	}

	defer C.BIO_free(bio)
	return bio_read_ECPrivateKey(bio, pass)
}

func PEM_Parse_ECPrivateKey(pem, pass string) (*EC_KEY, error) {
	cpem := C.CString(pem)
	defer C.free(unsafe.Pointer(cpem))

	bio := C.BIO_new_mem_buf(unsafe.Pointer(cpem), -1)
	if bio == nil {
		return nil, ErrKey
	}

	defer C.BIO_free(bio)
	return bio_read_ECPrivateKey(bio, pass)
}

func bio_read_ECPrivateKey(bio *C.BIO, pass string) (*EC_KEY, error) {
	cpass := C.CString(pass)
	defer C.free(unsafe.Pointer(cpass))

	pkey := C.PEM_read_bio_PrivateKey(bio, nil, nil, unsafe.Pointer(cpass))
	if pkey == nil {
		return nil, ErrKey
	}

	ret := &EC_KEY{C.EVP_PKEY_get1_EC_KEY(pkey)}
	runtime.SetFinalizer(ret, func(ret *EC_KEY) {
		C.EC_KEY_free(ret.eckey)
	})

	return ret, nil
}

func (k *EC_KEY) Check() bool {
	return (1 == C.EC_KEY_check_key(k.eckey))
}

func EC_KEY2Publickey(key *EC_KEY) (*PublicKey, error) {
	pkey := C.EVP_PKEY_new()
	if 1 != C.EVP_PKEY_set1_EC_KEY(pkey, key.eckey) {
		return nil, ErrKey
	}

	ret := &PublicKey{pkey}
	runtime.SetFinalizer(ret, func(ret *PublicKey) {
		C.EVP_PKEY_free(ret.pkey)
	})
	return ret, nil
}

func Publickey2EC_KEY(pubkey *PublicKey) *EC_KEY {
	ret := &EC_KEY{C.EVP_PKEY_get1_EC_KEY(pubkey.pkey)}
	runtime.SetFinalizer(ret, func(ret *EC_KEY) {
		C.EC_KEY_free(ret.eckey)
	})

	return ret
}

func FromHexKey(hexkey string) (*EC_KEY, error) {
	chexkey := C.CString(hexkey)
	defer C.free(unsafe.Pointer(chexkey))

	ret := &EC_KEY{eckey: C.FromHexKey(_nid_, chexkey)}
	runtime.SetFinalizer(ret, func(ret *EC_KEY) {
		C.EC_KEY_free(ret.eckey)
	})

	return ret, nil
}
